using System;
using System.Collections.Generic;
using System.Threading;

using TJDevHouse.Starcraft.AIModuleConnector.ImportExport;
using TJDevHouse.Starcraft.Game;

namespace TJDevHouse.Starcraft.AIModuleConnector
{
	/// <summary> StarCraft AI Interface.
	/// 
	/// Maintains StarCraft state and provides hooks for StarCraft commands.
	/// 
	/// Note: all coordinates are specified in tile coordinates.
	/// 
	/// Bulk of this code is taken from the .NET proxy bot: http://breakablec.redirectme.net/svn/repos/trunk/StarProxyBot_net/,
	/// which in turn was a conversion from the Java proxy bot: http://code.google.com/p/bwapi-proxy/.
	/// 
	/// </summary>
	public class Connector
	{

		/// <summary> Returns the singleton instance of ProxyBot.</summary>
		public static Connector Connection
		{
			get
			{
				if (_thisInstance == null)
				{
					_thisInstance = new Connector();
				}
				return _thisInstance;
			}

		}

        //static StarCraftFrame frame;

		/// <summary>allow the user to control units </summary>
		public static bool AllowUserControl = true;
		
		/// <summary>turn on complete information </summary>
		public static bool CompleteInformation = false;
		
		/// <summary>log a verbose amount of data </summary>
		public static bool VerboseLogging = false;
		
		/// <summary>Start up the StarCraft agent class </summary>
		public static bool RunAgent = false;
		
		/// <summary>port to start the server socket on </summary>
		public static int Port = 13337;
		
		
		/// <summary>queued up commands (orders) to send to StarCraft </summary>
		private System.Collections.ArrayList _commandQueue = new System.Collections.ArrayList();
		
		/// <summary>message number of commands to send to starcraft per response </summary>
		private int _maxCommandsPerMessage = 20;
		
		/// <summary>ProxyBot is a singleton </summary>
		private static Connector _thisInstance;

		public System.Net.Sockets.TcpListener ServerSocket;
		public System.IO.StreamReader Reader;

		public bool RequestStop { get; set; }

		



		/// <summary>
		/// The game object for this connection
		/// </summary>
		public Game.Game Game { get; set; }

		/// <summary>
		/// Whether a connection has been made, the initial hand shake completed and
		/// game fully initiated.
		/// </summary>
		public bool IsGameRunning
		{
			get
			{
				return Game != null;
			}
		}

		/// <summary>
		/// Whether to automatically save the map's terrain info
		/// </summary>
		public bool AutoSaveMapData { get; set; }

		/// <summary>
		/// Whether to force an overwrite of existing map terrain data files
		/// when saving automatically saving map data.
		/// </summary>
		public bool ForceAutoSaveMapDataOverwrite { get; set; }


		private Connector()
		{
			RequestStop = false;
			AutoSaveMapData = true;
			ForceAutoSaveMapDataOverwrite = false;
		}
		
		/// <summary> Starts the proxy bot.</summary>
		/*[STAThread]*/
		public static void  Main(string[] args)
		{
			Connector proxyBot = Connector.Connection;
			
			try
			{
				proxyBot.start();
			}
			catch (System.Net.Sockets.SocketException e)
			{
				//throw;
				Console.Error.WriteLine(e);
				System.Environment.Exit(0);
			}
			catch (System.Exception e)
			{
				//throw;
				Console.Error.WriteLine(e);
			}
		}
		

       
		
		/// <summary> Starts up a server socket and waits for StarCraft to initiate communication.
		/// 
		/// StarCraft sends the ProxyBot several messages about unit type, upgrades, locations.
		/// 
		/// Then the main communication loop begins. The ProxyBot waits for status upgrades from StarCraft
		/// and then sends queued up commands. The socket on the StarCraft end is a blocking socket, so 
		/// the ProxyBot will cause StarCraft to pause if it doesn't immediately respond. 
		/// </summary>
		public virtual void  start()
		{
			System.Net.Sockets.TcpClient clientSocket;
			string line;

			Console.WriteLine("Creating TCP Listener...");
			CreateTCPListener();
			Console.WriteLine("...done creating TCP Listener");

			Console.WriteLine("Receiving initial client connection...");
			ReceiveInitialClientConnection(out clientSocket, out line);
			Console.WriteLine("...done receiving initial client connection");
			
			// send startup flags
			Console.WriteLine("Sending startup options...");
			SendBotStartupOptions(clientSocket);
			Console.WriteLine("...done sending startup options");
			
			// get the rest of the data
			Console.WriteLine("Getting game data...");
			ReceiveInitialGameData(line);
			Console.WriteLine("...done getting game data");

			// begin the communication loop
			Console.WriteLine("Beginning communications loop.");
			DoCommunicationLoop(clientSocket, line);
			//Console.WriteLine("Communications loop ended.");
		}


		private void ReceiveInitialGameData(string line)
		{
			string forcesData = Reader.ReadLine();
			string unitTypeData = Reader.ReadLine();
			string locationData = Reader.ReadLine();
			string mapData = Reader.ReadLine();
			string mineralData = Reader.ReadLine();
			string gasData = Reader.ReadLine();
			string techTypeData = Reader.ReadLine();
			string upgradeTypeData = Reader.ReadLine();

			RawMapDataImporter mapDataImporter = new RawMapDataImporter();
			RawForcesDataImporter forcesDataImporter = new RawForcesDataImporter();
			RawUnitTypeDataImporter unitTypeDataImporter = new RawUnitTypeDataImporter();

			Console.WriteLine("Importing forces data...");
			List<TJDevHouse.Starcraft.Game.Force> forces = forcesDataImporter.ParseRawForcesData(forcesData);
			Console.WriteLine("...done importing forces data");

			Console.WriteLine("Importing map data...");
			TJDevHouse.Starcraft.Game.Map map = mapDataImporter.CreateMap(mapData, mineralData, gasData, locationData);
			Console.WriteLine("...done importing map data");

			Console.WriteLine("Importing unit type data...");
			Dictionary<TJDevHouse.Starcraft.Game.UnitType, TJDevHouse.Starcraft.Game.UnitTypeSpec> unitTypes = unitTypeDataImporter.ParseUnitTypeData(unitTypeData);
			Console.WriteLine("...done importing unit type data");

			string[] Players = line.Split(":".ToCharArray());
			int PlayerID = System.Int32.Parse(Players[1]);

			Game = new TJDevHouse.Starcraft.Game.Game(map, unitTypes);
			foreach(TJDevHouse.Starcraft.Game.Force force in forces)
			{
				Game.AddForce(force);
			}

			TJDevHouse.Starcraft.Game.Player clientPlayer = Game.Players[PlayerID];
			Game.ClientPlayer = clientPlayer;

			if (AutoSaveMapData)
			{
				SaveMapTerrainData(ForceAutoSaveMapDataOverwrite);
			}
		}

		/// <summary>
		/// Save the current map's terrain data to a file.
		/// 
		/// If the map data already exists, can specify that the
		/// data should be overwritten.
		/// </summary>
		public void SaveMapTerrainData(bool forceOverwrite)
		{
			if (forceOverwrite || !MapDataStorageController.MapDataIsSaved(Game.Map))
			{
				MapDataStorageController.SaveCompactMapData(Game.Map);
			}
		}

		public void SaveMapTerrainData()
		{
			SaveMapTerrainData(false);
		}

		private void DoCommunicationLoop(System.Net.Sockets.TcpClient clientSocket, string line)
		{
			bool first = true;

			

			//keep track of the frames, and don't do any processing if the frame number has not changed...
			int lastFrame = -1;

			RawGameStatusImporter gameUpdater = new RawGameStatusImporter();

			while (true)
			{
				string statusData = Reader.ReadLine();
				if (line == null)
				{
					break;
				}

				gameUpdater.UpdateGameWithStatus(Game, statusData);

				// build the command send
				System.Text.StringBuilder commandData = new System.Text.StringBuilder("commands");
				lock (_commandQueue.SyncRoot)
				{
					int commandsAdded = 0;

					while (_commandQueue.Count > 0 && commandsAdded < _maxCommandsPerMessage)
					{
						commandsAdded++;
						System.Object tempObject;
						tempObject = _commandQueue[_commandQueue.Count - 1];
						_commandQueue.RemoveAt(_commandQueue.Count - 1);
						Command command = (Command)tempObject;
						//TODO:Check
						commandData.Append(":" + command.getCommand() + ";" + command.UnitID + ";" + command.Arg0 + ";" + command.Arg1 + ";" + command.Arg2);
					}
				}

				// send commands to the starcraft client
				sbyte[] temp_sbyteArray2;
				temp_sbyteArray2 = SupportClass.ToSByteArray(SupportClass.ToByteArray(commandData.ToString()));
				clientSocket.GetStream().Write(SupportClass.ToByteArray(temp_sbyteArray2), 0, temp_sbyteArray2.Length);

			}
		}



		

		private static void SendBotStartupOptions(System.Net.Sockets.TcpClient clientSocket)
		{
			string botOptions = (AllowUserControl ? "1" : "0") + (CompleteInformation ? "1" : "0");
			sbyte[] temp_sbyteArray;
			temp_sbyteArray = SupportClass.ToSByteArray(SupportClass.ToByteArray(botOptions));
			clientSocket.GetStream().Write(SupportClass.ToByteArray(temp_sbyteArray), 0, temp_sbyteArray.Length);
		}

		private void ReceiveInitialClientConnection(out System.Net.Sockets.TcpClient clientSocket, out string line)
		{
			System.Console.Out.Write("Waiting for client...");
			clientSocket = ServerSocket.AcceptTcpClient();
			System.Console.Out.WriteLine(" client connected");

			Reader = new System.IO.StreamReader(new System.IO.StreamReader(clientSocket.GetStream(), System.Text.Encoding.Default).BaseStream, new System.IO.StreamReader(clientSocket.GetStream(), System.Text.Encoding.Default).CurrentEncoding);

			System.Console.Out.Write("Waiting for client ACK message...");
			line = Reader.ReadLine();
			System.Console.Out.WriteLine(" message received");
		}

		private void CreateTCPListener()
		{
			System.Net.Sockets.TcpListener temp_tcpListener;
			temp_tcpListener = new System.Net.Sockets.TcpListener(System.Net.Dns.GetHostEntry(System.Net.Dns.GetHostName()).AddressList[0], Port);
			temp_tcpListener.Start();
			ServerSocket = temp_tcpListener;
		}


		
		/// <summary> Adds a command to the command queue.
		/// 
		/// </summary>
		/// <param name="command">- the command to execture, see the Orders enumeration
		/// </param>
		/// <param name="unitID">- the unit to control
		/// </param>
		/// <param name="arg0">- the first command argument
		/// </param>
		/// <param name="arg1">- the second command argument
		/// </param>
		/// <param name="arg2">- the third command argument
		/// </param>
		private void  doCommand(Command.StarCraftCommand command, int unitID, int arg0, int arg1, int arg2)
		{
			Console.WriteLine("Command: {0}({1}) - {2} {3} {4}", command.ToString(), unitID, arg0, arg1, arg2);
			lock (_commandQueue.SyncRoot)
			{
				_commandQueue.Add(new Command(command, unitID, arg0, arg1, arg2));
			}
		}

		#region Commands

		/// <summary>*******************************************************
		/// Commands
		/// *******************************************************
		/// </summary>
		
		/// <summary> Tells the unit to attack move the specific location (in tile coordinates).
		/// 
		/// // virtual bool attackMove(Position position) = 0;
		/// </summary>
		public virtual void  attackMove(int unitID, int x, int y)
		{
			doCommand(Command.StarCraftCommand.attackMove, unitID, x, y, 0);
		}
		
		/// <summary> Tells the unit to attack another unit.
		/// 
		/// // virtual bool attackUnit(Unit* target) = 0;
		/// </summary>
		public virtual void  attackUnit(int unitID, int targetID)
		{
			doCommand(Command.StarCraftCommand.attackUnit, unitID, targetID, 0, 0);
		}
		
		/// <summary> Tells the unit to right click (move) to the specified location (in tile coordinates).
		/// 
		/// // virtual bool rightClick(Position position) = 0;
		/// </summary>
		public virtual void  rightClick(int unitID, int x, int y)
		{
			doCommand(Command.StarCraftCommand.rightClick, unitID, x, y, 0);
		}
		
		/// <summary> Tells the unit to right click (move) on the specified target unit 
		/// (Includes resources).
		/// 
		/// // virtual bool rightClick(Unit* target) = 0;
		/// </summary>
		public virtual void  rightClick(int unitID, int targetID)
		{
			doCommand(Command.StarCraftCommand.rightClickUnit, unitID, targetID, 0, 0);
		}
		
		/// <summary> Tells the building to train the specified unit type.
		/// 
		/// // virtual bool train(UnitType type) = 0;
		/// </summary>
		public virtual void  train(int unitID, int typeID)
		{
			doCommand(Command.StarCraftCommand.train, unitID, typeID, 0, 0);
		}
		
		/// <summary> Tells a worker unit to construct a building at the specified location.
		/// 
		/// // virtual bool build(TilePosition position, UnitType type) = 0;
		/// </summary>
		public virtual void  build(int unitID, int tx, int ty, int typeID)
		{
			doCommand(Command.StarCraftCommand.build, unitID, tx, ty, typeID);
		}
		
		/// <summary> Tells the building to build the specified add on.
		/// 
		/// // virtual bool buildAddon(UnitType type) = 0;
		/// </summary>
		public virtual void  buildAddon(int unitID, int typeID)
		{
			doCommand(Command.StarCraftCommand.buildAddon, unitID, typeID, 0, 0);
		}
		
		/// <summary> Tells the building to research the specified tech type.
		/// 
		/// // virtual bool research(TechType tech) = 0;
		/// </summary>
		public virtual void  research(int unitID, int techTypeID)
		{
			doCommand(Command.StarCraftCommand.research, unitID, techTypeID, 0, 0);
		}
		
		/// <summary> Tells the building to upgrade the specified upgrade type.
		/// 
		/// // virtual bool upgrade(UpgradeType upgrade) = 0;
		/// </summary>
		public virtual void  upgrade(int unitID, int upgradeTypeID)
		{
			doCommand(Command.StarCraftCommand.upgrade, unitID, upgradeTypeID, 0, 0);
		}
		
		/// <summary> Orders the unit to stop moving. The unit will chase enemies that enter its vision.
		/// 
		/// // virtual bool stop() = 0;
		/// </summary>
		public virtual void  stop(int unitID)
		{
			doCommand(Command.StarCraftCommand.stop, unitID, 0, 0, 0);
		}
		
		/// <summary> Orders the unit to hold position. The unit will not chase enemies that enter its vision.
		/// 
		/// // virtual bool holdPosition() = 0;
		/// </summary>
		public virtual void  holdPosition(int unitID)
		{
			doCommand(Command.StarCraftCommand.holdPosition, unitID, 0, 0, 0);
		}
		
		/// <summary> Orders the unit to patrol between its current location and the specified location.
		/// 
		/// // virtual bool patrol(Position position) = 0;
		/// </summary>
		public virtual void  patrol(int unitID, int x, int y)
		{
			doCommand(Command.StarCraftCommand.patrol, unitID, x, y, 0);
		}
		
		/// <summary> Orders a unit to follow a target unit.
		/// 
		/// // virtual bool follow(Unit* target) = 0;
		/// </summary>
		public virtual void  follow(int unitID, int targetID)
		{
			doCommand(Command.StarCraftCommand.follow, unitID, targetID, 0, 0);
		}
		
		/// <summary> Sets the rally location for a building. 
		/// 
		/// // virtual bool setRallyPosition(Position target) = 0;
		/// </summary>
		public virtual void  setRallyPosition(int unitID, int x, int y)
		{
			doCommand(Command.StarCraftCommand.setRallyPosition, unitID, x, y, 0);
		}
		
		/// <summary> Sets the rally location for a building based on the target unit's current position.
		/// 
		/// // virtual bool setRallyUnit(Unit* target) = 0;
		/// </summary>
		public virtual void  setRallyUnit(int unitID, int targetID)
		{
			doCommand(Command.StarCraftCommand.setRallyUnit, unitID, targetID, 0, 0);
		}
		
		/// <summary> Instructs an SCV to repair a target unit.
		/// 
		/// // virtual bool repair(Unit* target) = 0;
		/// </summary>
		public virtual void  repair(int unitID, int targetID)
		{
			doCommand(Command.StarCraftCommand.repair, unitID, targetID, 0, 0);
		}
		
		/// <summary> Orders a zerg unit to morph to a different unit type.
		/// 
		/// // virtual bool morph(UnitType type) = 0;
		/// </summary>
		public virtual void  morph(int unitID, int typeID)
		{
			doCommand(Command.StarCraftCommand.morph, unitID, typeID, 0, 0);
		}
		
		/// <summary> Tells a zerg unit to burrow. Burrow must be upgraded for non-lurker units.
		/// 
		/// // virtual bool burrow() = 0;
		/// </summary>
		public virtual void  burrow(int unitID)
		{
			doCommand(Command.StarCraftCommand.burrow, unitID, 0, 0, 0);
		}
		
		/// <summary> Tells a burrowed unit to unburrow.
		/// 
		/// // virtual bool unburrow() = 0;
		/// </summary>
		public virtual void  unburrow(int unitID)
		{
			doCommand(Command.StarCraftCommand.unburrow, unitID, 0, 0, 0);
		}
		
		/// <summary> Orders a siege tank to siege.
		/// 
		/// // virtual bool siege() = 0;
		/// </summary>
		public virtual void  siege(int unitID)
		{
			doCommand(Command.StarCraftCommand.siege, unitID, 0, 0, 0);
		}
		
		/// <summary> Orders a siege tank to un-siege.
		/// 
		/// // virtual bool unsiege() = 0;
		/// </summary>
		public virtual void  unsiege(int unitID)
		{
			doCommand(Command.StarCraftCommand.unsiege, unitID, 0, 0, 0);
		}
		
		/// <summary> Tells a unit to cloak. Works for ghost and wraiths. 
		/// 
		/// // virtual bool cloak() = 0;
		/// </summary>
		public virtual void  cloak(int unitID)
		{
			doCommand(Command.StarCraftCommand.cloak, unitID, 0, 0, 0);
		}
		
		/// <summary> Tells a unit to decloak, works for ghosts and wraiths.
		/// 
		/// // virtual bool decloak() = 0;
		/// </summary>
		public virtual void  decloak(int unitID)
		{
			doCommand(Command.StarCraftCommand.decloak, unitID, 0, 0, 0);
		}
		
		/// <summary> Commands a Terran building to lift off.
		/// 
		/// // virtual bool lift() = 0;
		/// </summary>
		public virtual void  lift(int unitID)
		{
			doCommand(Command.StarCraftCommand.lift, unitID, 0, 0, 0);
		}
		
		/// <summary> Commands a terran building to land at the specified location.
		/// 
		/// // virtual bool land(TilePosition position) = 0;
		/// </summary>
		public virtual void  land(int unitID, int tx, int ty)
		{
			doCommand(Command.StarCraftCommand.land, unitID, tx, ty, 0);
		}
		
		/// <summary> Orders the transport unit to load the target unit.
		/// 
		/// // virtual bool load(Unit* target) = 0;
		/// </summary>
		public virtual void  load(int unitID, int targetID)
		{
			doCommand(Command.StarCraftCommand.load, unitID, targetID, 0, 0);
		}
		
		/// <summary> Orders a transport unit to unload the target unit at the current transport location.
		/// 
		/// // virtual bool unload(Unit* target) = 0;
		/// </summary>
		public virtual void  unload(int unitID, int targetID)
		{
			doCommand(Command.StarCraftCommand.unload, unitID, targetID, 0, 0);
		}
		
		/// <summary> Orders a transport to unload all units at the current location.
		/// 
		/// // virtual bool unloadAll() = 0;
		/// </summary>
		public virtual void  unloadAll(int unitID)
		{
			doCommand(Command.StarCraftCommand.unloadAll, unitID, 0, 0, 0);
		}
		
		/// <summary> Orders a unit to unload all units at the target location.
		/// 
		/// // virtual bool unloadAll(Position position) = 0;
		/// </summary>
		public virtual void  unloadAll(int unitID, int x, int y)
		{
			doCommand(Command.StarCraftCommand.unloadAllPosition, unitID, x, y, 0);
		}
		
		/// <summary> Orders a being to stop being constructed.
		/// 
		/// // virtual bool cancelConstruction() = 0;
		/// </summary>
		public virtual void  cancelConstruction(int unitID)
		{
			doCommand(Command.StarCraftCommand.cancelConstruction, unitID, 0, 0, 0);
		}
		
		/// <summary> Tells an scv to pause construction on a building.
		/// 
		/// // virtual bool haltConstruction() = 0;
		/// </summary>
		public virtual void  haltConstruction(int unitID)
		{
			doCommand(Command.StarCraftCommand.haltConstruction, unitID, 0, 0, 0);
		}
		
		/// <summary> Orders a zerg unit to stop morphing.
		/// 
		/// // virtual bool cancelMorph() = 0;
		/// </summary>
		public virtual void  cancelMorph(int unitID)
		{
			doCommand(Command.StarCraftCommand.cancelMorph, unitID, 0, 0, 0);
		}
		
		/// <summary> Tells a building to remove the last unit from its training queue.
		/// 
		/// // virtual bool cancelTrain() = 0;
		/// </summary>
		public virtual void  cancelTrain(int unitID)
		{
			doCommand(Command.StarCraftCommand.cancelTrain, unitID, 0, 0, 0);
		}
		
		/// <summary> Tells a building to remove a specific unit from its queue.
		/// 
		/// // virtual bool cancelTrain(int slot) = 0;
		/// </summary>
		public virtual void  cancelTrain(int unitID, int slot)
		{
			doCommand(Command.StarCraftCommand.cancelTrainSlot, unitID, slot, 0, 0);
		}
		
		/// <summary> Orders a Terran building to stop constructing an add on.
		/// 
		/// // virtual bool cancelAddon() = 0;
		/// </summary>
		public virtual void  cancelAddon(int unitID)
		{
			doCommand(Command.StarCraftCommand.cancelAddon, unitID, 0, 0, 0);
		}
		
		/// <summary> 
		/// Tells a building cancel a research in progress. 
		/// 
		/// // virtual bool cancelResearch() = 0;
		/// </summary>
		public virtual void  cancelResearch(int unitID)
		{
			doCommand(Command.StarCraftCommand.cancelResearch, unitID, 0, 0, 0);
		}
		
		/// <summary> 
		/// Tells a building cancel an upgrade  in progress. 
		/// 
		/// // virtual bool cancelUpgrade() = 0;
		/// </summary>
		public virtual void  cancelUpgrade(int unitID)
		{
			doCommand(Command.StarCraftCommand.cancelUpgrade, unitID, 0, 0, 0);
		}
		
		/// <summary> Tells the unit to use the specified tech, (i.e. STEM PACKS)
		/// 
		/// // virtual bool useTech(TechType tech) = 0;
		/// </summary>
		public virtual void  useTech(int unitID, int techTypeID)
		{
			doCommand(Command.StarCraftCommand.useTech, unitID, techTypeID, 0, 0);
		}
		
		/// <summary> Tells the unit to use tech at the target location.
		/// 
		/// Note: for AOE spells such as plague.
		/// 
		/// // virtual bool useTech(TechType tech, Position position) = 0;
		/// </summary>
		public virtual void  useTech(int unitID, int techTypeID, int x, int y)
		{
			doCommand(Command.StarCraftCommand.useTechPosition, unitID, techTypeID, x, y);
		}
		
		/// <summary> Tells the unit to use tech on the target unit.
		/// 
		/// Note: for targeted spells such as irradiate.
		/// 
		/// // virtual bool useTech(TechType tech, Unit* target) = 0;
		/// </summary>
		public virtual void  useTech(int unitID, int techTypeID, int targetID)
		{
			doCommand(Command.StarCraftCommand.useTechTarget, unitID, techTypeID, targetID, 0);
		}
		

		#endregion


		private class AgentThread
		{
			public AgentThread(Connector enclosingInstance)
			{
				InitBlock(enclosingInstance);
			}
			private void InitBlock(Connector enclosingInstance)
			{
				Enclosing_Instance = enclosingInstance;
			}
			
			public Connector Enclosing_Instance {get; private set;}

			public void Run()
			{
				Console.WriteLine("Starting AI Agent...");
				new StarCraftAgent().start();
				Console.WriteLine("...AI Agent started");
			}
		}
	}
}